﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.IO;
using System.Linq;
using OpenTK.Input;
using OptixDotNet;
using OptixDotNet.Math;
using OptixDotNet.Utils;
using OptixDotNetUtils;
using RayDen.Library.Data.Xml;
using RayDen.Library.Entity.Frames;
using RayDen.Optix.Engine.Engines;
using RayDen.Optix.Engine.Managers;
using RayDen.Optix.Engine.Scene;

namespace RayDen.Optix.Engine.Components
{
    public class RenderWindow : BaseWindow
    {
        private uint mFrame = 0;

        private OptixRenderConfig config;

        private int currentCamera;
        private float distanceOffset = 10.5f;
        private float apertureSize = 0.02f;
        private float fov = 30.0f;



        public RenderWindow(string framePath)
        {

            // this.SetDesktopBounds(100,100, 512, 384);


            Utils.DetectGPUCompatibility();
            UseSRGB = true;
            UsePBO = true;
            //config = SceneConfiguration.quartalScene;
            //config = crossRoadsScene;

            //config = FrameHandler.OpenFrame(@"F:\Dev\Frames\bsdft.MFF");

            //engine = new PMPathTracer(config.Cameras.First());
            config = new OptixRenderConfig();
            ImageSaveDir = ConfigManager.Instance.Get("ImageSavePath");
            FrameDescription frame;
            using (var frameFile = new FileStream(framePath, FileMode.Open, FileAccess.Read))
            {
                using (var reader = new StreamReader(frameFile))
                    frame = (FrameDescription)SerializationService.Deserialize(reader.ReadToEnd(), typeof(FrameDescription));
            }
            frame.FrameName = Path.GetFileNameWithoutExtension(framePath);
            config.OpenFrame(frame);
            var fs = config.Engine.Scene.Cameras.First();
            this.Camera = new Camera()
            {
                Position = fs.Position.ToVec3(),
                Target = fs.Direction.ToVec3(),
                Fov = fs.Fov,
                Up =
                    //new Vector3(0, 1f, 0)
                fs.Up.ToVec3()
            };

            currentCamera = 0;
        }


        private float SceneEpsilon = 0.0003f;


        public override string GetStatistics()
        {
            return string.Format("{0} \r\n {1}", base.GetStatistics(), config.GetStats());
        }

        protected override void Initialize()
        {
            this.Context = EngineContext.Instance.OptixContext;
            CreateOutputBuffer(Format.Float4);
            config.Engine.OutputBuffer = this.OutputBuffer;
            config.Engine.Initialize(this.SetCamera, config, Width, Height);

            //give aproximate here becuase we'll have somewhere between rrBeginDepth and maxDepth number of iterations per sample.
            int avgIteration = (int)(RenderEngine.rrBeginDepth + RenderEngine.maxDepth) / 2;
            RaysTracedPerFrame = Width * Height * 2 * ((int)RenderEngine.sqrtSamples * (int)RenderEngine.sqrtSamples) * avgIteration;

        }

        protected virtual bool LoadAccelData(OptixOBJLoader model, string sourcePath, string cachedAccelPath)
        {
            if (!File.Exists(sourcePath) || !File.Exists(cachedAccelPath))
                return false;

            if (File.GetLastWriteTime(sourcePath) > File.GetLastWriteTime(cachedAccelPath))
                return false;

            return model.GeoGroup.Acceleration.Load(cachedAccelPath);
        }

        protected override void Keyboard_KeyDown(object sender, OpenTK.Input.KeyboardKeyEventArgs e)
        {
            base.Keyboard_KeyDown(sender, e);
            config.Engine.OnKeyPressed(e);
            switch (e.Key)
            {
                case Key.Number0:
                    if (currentCamera > 0)
                        currentCamera--;
                    this.SetCamera(new BoundingBox());
                    break;
                //case Key.Number1:
                //    if (currentCamera < config.Cameras.Length)
                //        currentCamera++;
                //    this.SetCamera(new BoundingBox());
                //    break;
                case Key.F3:
                    this.SaveFrame("frame_01.xml");
                    return;

                case Key.Keypad7:
                    distanceOffset -= 0.01f;
                    Console.WriteLine(" distanceOffset = {0}", distanceOffset);
                    break;
                case Key.Keypad9:
                    distanceOffset += 0.01f;
                    Console.WriteLine("distanceOffset = {0}", distanceOffset);

                    break;

                case Key.Keypad8:
                    apertureSize -= 0.01f;
                    Console.WriteLine("apertureSize = {0}", apertureSize);

                    break;
                case Key.Keypad5:
                    apertureSize += 0.01f;
                    Console.WriteLine("apertureSize = {0}", apertureSize);

                    break;


            }
            CameraUpdate();
        }




        protected void SetCamera(BoundingBox box)
        {
            Camera.Fov = 80f;

            Camera.LookAt(config.Engine.Scene.Cameras[currentCamera].Position.ToVec3(),
                          config.Engine.Scene.Cameras[currentCamera].Direction.ToVec3(),
                          config.Engine.Scene.Cameras[currentCamera].Up.ToVec3());
            Camera.Aspect = (float)Width / (float)Height;
            Camera.RotationVel = 100.0f;
            Camera.TranslationVel = 100.0f;

            //Camera.CenterOnBoundingBox(box, .95f);

            //frameManager = new FrameManager(Camera.Position, Camera.Look, Camera.Position+new Vector3(40,0,0), Camera.Look, this);
            //frameManager.Reset();
            CameraUpdate();
        }

        protected override void RayTrace()
        {
            Context["frame_number"].Set(mFrame++);
            //frameManager.StartRendering();
            Context.Launch(0, (uint)Width, (uint)Height);
        }

        protected override void OnClosed(EventArgs e)
        {

        }

        protected override void CameraUpdate()
        {
            mFrame = 0;

            Vector3 eye = Camera.Position;
            Vector3 right = Camera.Right;
            Vector3 up = Camera.Up;
            Vector3 look = Camera.Look;
            float fov = Camera.Fov;

            Context["eye"].Set(ref eye);
            Context["U"].Set(ref right);
            Context["V"].Set(ref up);
            Context["W"].Set(ref look);

            float focal_distance = look.Length() + distanceOffset;
            focal_distance = Math.Max(focal_distance, this.SceneEpsilon);
            float focal_scale = focal_distance / look.Length();
            Context["focal_scale"].Set(focal_scale);
            Context["aperture_radius"].Set(apertureSize);
            var extinction = 1f;
            Context["extinction"].Set(extinction);
            config.Engine.UpdateFrame();
        }

        protected override void OnClosing(CancelEventArgs e)
        {

        }




        public override void Dispose()
        {
            this.config.Dispose();
            base.Dispose();
        }

        private void SaveFrame(string fileName)
        {
            //    FrameFactory.SaveDataFile(fileName, "Scene", new Dictionary<string, object>() {
            //        {
            //          "Camera.Position",
            //          this.Camera.Position
            //          },
            //        {
            //          "Camera.Target",
            //          this.Camera.Target
            //          },
            //        {
            //          "Camera.Up",
            //          this.Camera.Up
            //          },
            //        {
            //          "ToneMap.yMax",
            //          1f
            //          },
            //        {
            //          "ToneMap.yMax",
            //          2f
            //          }
            //    });
        }
    }
}
